fix(nextjs-mf): await async factories and return wrapper factory in server onLoad - #4974
fix(nextjs-mf): await async factories and return wrapper factory in server onLoad#4974shashank-u03 wants to merge 2 commits into
Conversation
🦋 Changeset detectedLatest commit: 57cacb9 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
eb80f2d to
194e558
Compare
|
Hey @ScriptedAlchemy @2heal1 - this fixes the Promise.prototype.then crash from #2218 (root cause + fix details in the description above). Async factories were getting Proxy-wrapped before being awaited, which shadowed .then() and broke webpack's async module runtime. Added tests for the async/sync paths and rejection handling. Would appreciate a review when you get a chance! |
| if (exposeModuleFactory) { | ||
| const wrappedExports = exposedModuleExports; | ||
| return function () { | ||
| return wrappedExports; |
There was a problem hiding this comment.
Returning wrappedExports as the module factory makes the namespace proxy observable to webpack. The proxy's get trap currently replaces every function export with a plain wrapper that
invokes originalMethod.apply(...), which does not preserve constructor/class semantics.
For example, an async factory resolving to:
{ default: class RemoteComponent {} }
now produces an export where new exports.default() throws:
TypeError: Class constructor RemoteComponent cannot be invoked without 'new'
This can break default-exported React class components and other constructible exports during SSR/build. Could we preserve both call and construct behavior, for example by proxying exported
functions with apply and construct traps using Reflect.apply/Reflect.construct? Please also add a regression test verifying that a class returned by an async factory remains
constructible and preserves instanceof.
There was a problem hiding this comment.
Addressed in e7e1e0f replaced the .apply()-based wrapper with wrapCallableForChunkTracking, a Proxy using apply/construct traps (Reflect.apply/Reflect.construct), so constructible exports work through the proxy on the server.
Added the regression test you asked for: keeps class default export constructible after async factory, asserting new exports.default() + instanceof both hold. Also covered usedChunks tracking on construction and static-prop/plain-function cases.
Note: static methods on the wrapped default (exports.default.someStatic()) aren't individually tracked anymore, only top-level apply/construct marks usedChunks. Since tracking is at remote/expose granularity, not per-method, this shouldn't matter in practice, but flag if you'd want it covered explicitly.
… server onLoad proxy
8c57991 to
e7e1e0f
Compare
Description
Remote containers in
@module-federation/runtimeexpose async module factories (RemoteEntryExports.getreturns() => Promise<Module>).During the webpack build/SSR path,
runtime-coreloads remotes withloadFactory: falseandfrom: 'build'(seepackages/webpack-bundler-runtime/src/remotes.ts), passing the unexecuted factory tonextjs-mf'sonLoadhook asexposeModuleFactory.On the server,
onLoadsynchronously invoked that factory and Proxy-wrapped the return value for chunk-usage tracking. When the factory is async, the sync invocation returned a rawPromise. Proxy-wrapping thatPromisecaused webpack's async module runtime to fail when calling.then():TypeError: Method Promise.prototype.then called on incompatible receiver [object Promise]
runtime-corecatches this inloadRemoteand routes it toerrorLoadRemotewithlifecycle: 'onLoad'andfrom: 'build'.Fix:
Promiseresults frommoduleOrFactory()before applying the existing Proxy wrapper on the server.exposeModuleFactory, return a wrapper factory soloadRemotecan adopt the proxied result (runtime-coreonly usesonLoad's return when it is a function - seepackages/runtime-core/src/remote/index.ts).runtime-corealready handles async factories inmodule.wraperFactory; this alignsnextjs-mfonLoadwith that behavior.Tests added in
runtimePlugin.test.ts:Promise.prototype.thenis not broken after the fixexposeModuleFactorystill workswindowpresent) still returnsargsunchangedNote on
async onLoad:loadRemotealwaysawaitsonLoad.emit()andAsyncHook.emitwraps listener returns withPromise.resolve(). The client branch still returnsargsunchanged; only the server branch gained internalawaitlogic.Related Issue
#2218
Types of changes
Checklist